package com.elminster.common.db.analyze;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import com.elminster.common.constants.RegexConstants;
import com.elminster.common.db.annotation.Sql;
import com.elminster.common.db.annotation.SqlFile;
import com.elminster.common.db.annotation.SqlParam;
import com.elminster.common.db.exception.SqlAnalyzeException;
import com.elminster.common.util.ReflectUtil;
import com.elminster.common.util.StringUtil;

/**
 * A simple SQL analyzer implement.
 * 
 * @author Gu
 * @version 1.0
 *
 */
public class SimpleSqlAnalyzer extends BaseSqlAnalyzer implements ISqlAnalyzer {
	
	private static final char SPACE = ' ';
	private static final char COMMA = ',';
	private static final char DOT = '.';
	private static final String SQL_REPLACEMENT = "?";
	private static final String PARAM_KEY = "$";
	private static final String PARAM_KEY_REGEX = RegexConstants.REGEX_DOLLAR;
	
	/**
	 * Constructor
	 */
	public SimpleSqlAnalyzer() {}

	/**
	 * Parser a method invocation to get the SQL statement and the SQL statement's parameter(s).
	 * @param method the method which original SQL statement is invoked
	 * @param methodArguments the method's argument(s)
	 */
	@Override
	public void parser(Method invokedMethod, Object... methodArguments) {
		String originalSql = getOriginalSql(invokedMethod);
		// get all SqlParam annotation(s)
		Annotation[][] parametersAnnos = invokedMethod.getParameterAnnotations();
		Map<String, Object> paramAnnoMap = new HashMap<String, Object>();
		for (int i = 0; i < parametersAnnos.length; i++) {
			Annotation[] parameterAnnos = parametersAnnos[i];
			if (null != parameterAnnos) {
				for (Annotation anno : parameterAnnos) {
					if (anno.annotationType().equals(SqlParam.class)) {
						SqlParam sqlParamAnno = (SqlParam) anno;
						paramAnnoMap.put(sqlParamAnno.value(), methodArguments[i]);
					}
				}
			}
		}
		// parser SQL
		analyzedSql = new String(originalSql);
		int idx = -1;
		while ((idx = analyzedSql.indexOf(PARAM_KEY)) > 0) {
			boolean isSimpleData = true;
			StringBuilder builder = new StringBuilder();
			for (int i = idx + PARAM_KEY.length(); i < analyzedSql.length(); i++) {
				char ch = analyzedSql.charAt(i);
				if (SPACE == ch || COMMA == ch) {
					break;
				} else {
					if (DOT == ch) {
						isSimpleData = false;
					}
					builder.append(ch);
				}
			}
			String replaceKey = builder.toString();
			if (isSimpleData) {
				Object replaceValue = paramAnnoMap.get(replaceKey);
				if (null == replaceValue) {
					throw new SqlAnalyzeException("SqlParam Annotation's value and argurment are NOT matched.");
				}
				analyzedSqlParameters.add(replaceValue);
			} else {
				String[] splited = replaceKey.split(RegexConstants.REGEX_DOT);
				if (2 != splited.length) {
					throw new SqlAnalyzeException("");
				}
				Object obj = paramAnnoMap.get(splited[0]);
				if (null == obj) {
					throw new SqlAnalyzeException("SqlParam Annotation's value and argurment are NOT matched.");
				}
				try {
					Object fieldValue = ReflectUtil.getFieldValue(obj, splited[1]);
					analyzedSqlParameters.add(fieldValue);
				} catch (IllegalArgumentException e) {
					throw new SqlAnalyzeException(e);
				} catch (IllegalAccessException e) {
					throw new SqlAnalyzeException(e);
				}
			}
			analyzedSql = analyzedSql.replaceFirst(PARAM_KEY_REGEX + replaceKey, SQL_REPLACEMENT);
		}
	}
	
	/**
	 * Get the original SQL statement which to analyze.
	 * @param method the method which original SQL statement is invoked
	 * @return the original SQL statement which to analyze
	 */
	private String getOriginalSql(Method method) {
		String originalSql = null;
		// Get SQL from SQL annotation
		Sql sqlAnno = method.getAnnotation(Sql.class);
		SqlFile sqlFileAnno = method.getAnnotation(SqlFile.class);
		if (null == sqlAnno) {
			// TODO Error
			throw new SqlAnalyzeException("");
		} else {
			String annoValue = null == sqlAnno ? null : sqlAnno.value();
			String annoFile = null == sqlFileAnno ? null : sqlFileAnno.value();
			if (!StringUtil.isEmpty(annoValue)) {
				originalSql = annoValue;
			} else if (!StringUtil.isEmpty(annoFile)) {
				originalSql = loadSqlFile(annoFile);
			} else {
				// TODO Error
				throw new SqlAnalyzeException("");
			}
		}
		return originalSql;
	}
	
	/**
	 * Load the SQL statement from a specified SQL file
	 * @param fileName specified SQL file
	 * @return SQL statement
	 */
	private String loadSqlFile(String fileName) {
		// TODO Auto-generated method stub
		return null;
	}
}
